Add an installation script to the repo
authorAlex Crichton <alex@alexcrichton.com>
Mon, 14 Jul 2014 15:06:14 +0000 (08:06 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Mon, 14 Jul 2014 15:18:49 +0000 (08:18 -0700)
This updates the `make install` target and adds a new `make dist` target which
will prepare a distributable tarball with an install script. All work is based
off the equivalent rust nightly tarball and nightly installation scripts.

Closes #159
Closes #184

Makefile
README.md
src/bin/cargo.rs
src/install.sh [new file with mode: 0755]

index e10fedb1aafdf69c801f23db1390ec4f2a140f84..0bab186afddcc0409950806c2a3ddd97f5f10d0e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,8 @@
 RUSTC_FLAGS ?=
 DESTDIR ?=
 PREFIX ?= /usr/local
-BINDIR ?= $(PREFIX)/bin
+TARGET ?= target
+PKG_NAME ?= cargo-nightly
 
 ifeq ($(wildcard rustc/bin),)
 export RUSTC := rustc
@@ -29,8 +30,11 @@ DEPS = -L libs/hammer.rs/target -L libs/toml-rs/build
 TOML = libs/toml-rs/build/$(shell $(RUSTC) --print-file-name libs/toml-rs/src/toml.rs)
 HAMMER = libs/hammer.rs/target/$(shell $(RUSTC) --crate-type=lib --print-file-name libs/hammer.rs/src/hammer.rs)
 HAMCREST = libs/hamcrest-rust/target/libhamcrest.timestamp
-LIBCARGO = target/libcargo.timestamp
-BIN_TARGETS = $(patsubst %,target/%,$(BINS))
+LIBCARGO = $(TARGET)/libcargo.rlib
+TESTDIR = $(TARGET)/tests
+DISTDIR = $(TARGET)/dist
+PKGDIR = $(DISTDIR)/$(PKG_NAME)
+BIN_TARGETS = $(BINS:%=$(TARGET)/%)
 
 all: $(BIN_TARGETS)
 
@@ -45,36 +49,39 @@ $(TOML): $(wildcard libs/toml-rs/src/*.rs)
 $(HAMCREST): $(shell find libs/hamcrest-rust/src/hamcrest -name '*.rs')
        $(MAKE) -C libs/hamcrest-rust
 
+$(TARGET)/:
+       mkdir -p $@
+
+$(TESTDIR)/:
+       mkdir -p $@
+
 # === Cargo
 
-$(LIBCARGO): $(SRC) $(HAMMER) $(TOML)
-       mkdir -p target
-       $(RUSTC) $(RUSTC_FLAGS) $(DEPS) --out-dir target src/cargo/lib.rs
-       touch $(LIBCARGO)
+$(LIBCARGO): $(SRC) $(HAMMER) $(TOML) | $(TARGET)/
+       $(RUSTC) $(RUSTC_FLAGS) $(DEPS) --out-dir $(TARGET) src/cargo/lib.rs
 
 libcargo: $(LIBCARGO)
 
 # === Commands
 
-$(BIN_TARGETS): target/%: src/bin/%.rs $(HAMMER) $(TOML) $(LIBCARGO)
-       $(RUSTC) $(RUSTC_FLAGS) $(DEPS) -Ltarget --out-dir target $<
+$(BIN_TARGETS): $(TARGET)/%: src/bin/%.rs $(HAMMER) $(TOML) $(LIBCARGO)
+       $(RUSTC) $(RUSTC_FLAGS) $(DEPS) -L$(TARGET) --out-dir $(TARGET) $<
 
 # === Tests
 
 TEST_SRC = $(shell find tests -name '*.rs')
 TEST_DEPS = $(DEPS) -L libs/hamcrest-rust/target
 
-target/tests/test-integration: $(HAMCREST) $(TEST_SRC) $(BIN_TARGETS)
-       $(RUSTC) --test --crate-type=lib $(TEST_DEPS) -Ltarget -o $@  tests/tests.rs
+$(TESTDIR)/test-integration: $(HAMCREST) $(TEST_SRC) $(BIN_TARGETS) | $(TESTDIR)/
+       $(RUSTC) --test $(TEST_DEPS) -L$(TARGET) -o $@ tests/tests.rs
 
-target/tests/test-unit: $(TOML) $(HAMCREST) $(SRC) $(HAMMER)
-       mkdir -p target/tests
+$(TESTDIR)/test-unit: $(TOML) $(HAMCREST) $(SRC) $(HAMMER) | $(TESTDIR)/
        $(RUSTC) --test -g $(RUSTC_FLAGS) $(TEST_DEPS) -o $@ src/cargo/lib.rs
 
-test-unit: target/tests/test-unit
-       target/tests/test-unit $(only)
+test-unit: $(TESTDIR)/test-unit
+       $< $(only)
 
-test-integration: target/tests/test-integration
+test-integration: $(TESTDIR)/test-integration
        $< $(only)
 
 test: test-unit test-integration style no-exes
@@ -88,14 +95,42 @@ no-exes:
                && exit 1 || exit 0
 
 clean:
-       rm -rf target
+       rm -rf $(TARGET)
 
 clean-all: clean
        git submodule foreach make clean
 
-install:
-       install -d $(DESTDIR)$(BINDIR)
-       install target/cargo target/cargo-* $(DESTDIR)$(BINDIR)
+dist: $(DISTDIR)/$(PKG_NAME).tar.gz
+
+distcheck: dist
+       rm -rf $(TARGET)/distcheck
+       mkdir -p $(TARGET)/distcheck
+       (cd $(TARGET)/distcheck && tar xf ../dist/$(PKG_NAME).tar.gz)
+       $(TARGET)/distcheck/$(PKG_NAME)/install.sh \
+               --prefix=$(TARGET)/distcheck/install
+       $(TARGET)/distcheck/install/bin/cargo -h > /dev/null
+       $(TARGET)/distcheck/$(PKG_NAME)/install.sh \
+               --prefix=$(TARGET)/distcheck/install --uninstall
+       [ -f $(TARGET)/distcheck/install/bin/cargo ] && exit 1 || exit 0
+
+$(DISTDIR)/$(PKG_NAME).tar.gz: $(PKGDIR)/lib/cargo/manifest.in
+       tar -czvf $@ -C $(DISTDIR) $(PKG_NAME)
+
+$(PKGDIR)/lib/cargo/manifest.in: $(BIN_TARGETS) Makefile
+       rm -rf $(PKGDIR)
+       mkdir -p $(PKGDIR)/bin $(PKGDIR)/lib/cargo
+       cp $(TARGET)/cargo $(PKGDIR)/bin
+       cp $(BIN_TARGETS) $(PKGDIR)/lib/cargo
+       rm $(PKGDIR)/lib/cargo/cargo
+       (cd $(PKGDIR) && find . -type f | sed 's/^\.\///') \
+               > $(DISTDIR)/manifest-$(PKG_NAME).in
+       cp src/install.sh $(PKGDIR)
+       cp README.md LICENSE-MIT LICENSE-APACHE $(PKGDIR)
+       cp LICENSE-MIT $(PKGDIR)
+       mv $(DISTDIR)/manifest-$(PKG_NAME).in $(PKGDIR)/lib/cargo/manifest.in
+
+install: $(PKGDIR)/lib/cargo/manifest.in
+       $(PKGDIR)/install.sh --prefix=$(PREFIX)
 
 # Setup phony tasks
 .PHONY: all clean distclean test test-unit test-integration libcargo style
index cef99a6d4c102d8d3f3ca605165aa1140df7b0b0..fe097f865b327be117453a07d08a4f814d758f3c 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@ Cargo downloads your Rust project’s dependencies and compiles your project.
 
 Learn more at http://crates.io/.
 
-## Installing cargo
+## Installing cargo from nightlies
 
 Cargo has nightlies available for use. The cargo source is not always guaranteed
 to compile on rust master as it may lag behind by a day or two. Nightlies,
@@ -11,7 +11,7 @@ however, will run regardless of this fact!
 ```
 $ curl -O http://static.rust-lang.org/cargo-dist/cargo-nightly-linux.tar.gz
 $ tar xf cargo-nightly-linux.tar.gz
-$ ./cargo-nightly/bin/cargo build
+$ ./cargo-nightly/install.sh
 ```
 
 The current nightlies available are:
index 8379a85eb710ce2480c767e70366829c172227de..b21b416aac4e35919486b83e7cbf03783577f3fa 100644 (file)
@@ -58,9 +58,20 @@ fn execute() {
             println!("Options (for all commands):\n\n{}", options);
         },
         _ => {
-            let command = format!("cargo-{}", cmd);
+            let command = format!("cargo-{}{}", cmd, os::consts::EXE_SUFFIX);
             let mut command = match os::self_exe_path() {
-                Some(path) => Command::new(path.join(command)),
+                Some(path) => {
+                    let p1 = path.join("../lib/cargo").join(command.as_slice());
+                    let p2 = path.join(command.as_slice());
+                    println!("{} {}", p1.display(), p2.display());
+                    if p1.exists() {
+                        Command::new(p1)
+                    } else if p2.exists() {
+                        Command::new(p2)
+                    } else {
+                        Command::new(command)
+                    }
+                }
                 None => Command::new(command),
             };
             let command = command
diff --git a/src/install.sh b/src/install.sh
new file mode 100755 (executable)
index 0000000..79e7a32
--- /dev/null
@@ -0,0 +1,472 @@
+#!/bin/sh
+# Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+# file at the top-level directory of this distribution and at
+# http://rust-lang.org/COPYRIGHT.
+#
+# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+# option. This file may not be copied, modified, or distributed
+# except according to those terms.
+
+msg() {
+    echo "install: $1"
+}
+
+step_msg() {
+    msg
+    msg "$1"
+    msg
+}
+
+warn() {
+    echo "install: WARNING: $1"
+}
+
+err() {
+    echo "install: error: $1"
+    exit 1
+}
+
+need_ok() {
+    if [ $? -ne 0 ]
+    then
+        err "$1"
+    fi
+}
+
+need_cmd() {
+    if command -v $1 >/dev/null 2>&1
+    then msg "found $1"
+    else err "need $1"
+    fi
+}
+
+putvar() {
+    local T
+    eval T=\$$1
+    eval TLEN=\${#$1}
+    if [ $TLEN -gt 35 ]
+    then
+        printf "install: %-20s := %.35s ...\n" $1 "$T"
+    else
+        printf "install: %-20s := %s %s\n" $1 "$T" "$2"
+    fi
+}
+
+valopt() {
+    VAL_OPTIONS="$VAL_OPTIONS $1"
+
+    local OP=$1
+    local DEFAULT=$2
+    shift
+    shift
+    local DOC="$*"
+    if [ $HELP -eq 0 ]
+    then
+        local UOP=$(echo $OP | tr '[:lower:]' '[:upper:]' | tr '\-' '\_')
+        local V="CFG_${UOP}"
+        eval $V="$DEFAULT"
+        for arg in $CFG_ARGS
+        do
+            if echo "$arg" | grep -q -- "--$OP="
+            then
+                val=$(echo "$arg" | cut -f2 -d=)
+                eval $V=$val
+            fi
+        done
+        putvar $V
+    else
+        if [ -z "$DEFAULT" ]
+        then
+            DEFAULT="<none>"
+        fi
+        OP="${OP}=[${DEFAULT}]"
+        printf "    --%-30s %s\n" "$OP" "$DOC"
+    fi
+}
+
+opt() {
+    BOOL_OPTIONS="$BOOL_OPTIONS $1"
+
+    local OP=$1
+    local DEFAULT=$2
+    shift
+    shift
+    local DOC="$*"
+    local FLAG=""
+
+    if [ $DEFAULT -eq 0 ]
+    then
+        FLAG="enable"
+    else
+        FLAG="disable"
+        DOC="don't $DOC"
+    fi
+
+    if [ $HELP -eq 0 ]
+    then
+        for arg in $CFG_ARGS
+        do
+            if [ "$arg" = "--${FLAG}-${OP}" ]
+            then
+                OP=$(echo $OP | tr 'a-z-' 'A-Z_')
+                FLAG=$(echo $FLAG | tr 'a-z' 'A-Z')
+                local V="CFG_${FLAG}_${OP}"
+                eval $V=1
+                putvar $V
+            fi
+        done
+    else
+        if [ ! -z "$META" ]
+        then
+            OP="$OP=<$META>"
+        fi
+        printf "    --%-30s %s\n" "$FLAG-$OP" "$DOC"
+     fi
+}
+
+flag() {
+    BOOL_OPTIONS="$BOOL_OPTIONS $1"
+
+    local OP=$1
+    shift
+    local DOC="$*"
+
+    if [ $HELP -eq 0 ]
+    then
+        for arg in $CFG_ARGS
+        do
+            if [ "$arg" = "--${OP}" ]
+            then
+                OP=$(echo $OP | tr 'a-z-' 'A-Z_')
+                local V="CFG_${OP}"
+                eval $V=1
+                putvar $V
+            fi
+        done
+    else
+        if [ ! -z "$META" ]
+        then
+            OP="$OP=<$META>"
+        fi
+        printf "    --%-30s %s\n" "$OP" "$DOC"
+     fi
+}
+
+validate_opt () {
+    for arg in $CFG_ARGS
+    do
+        isArgValid=0
+        for option in $BOOL_OPTIONS
+        do
+            if test --disable-$option = $arg
+            then
+                isArgValid=1
+            fi
+            if test --enable-$option = $arg
+            then
+                isArgValid=1
+            fi
+            if test --$option = $arg
+            then
+                isArgValid=1
+            fi
+        done
+        for option in $VAL_OPTIONS
+        do
+            if echo "$arg" | grep -q -- "--$option="
+            then
+                isArgValid=1
+            fi
+        done
+        if [ "$arg" = "--help" ]
+        then
+            echo
+            echo "No more help available for Configure options,"
+            echo "check the Wiki or join our IRC channel"
+            break
+        else
+            if test $isArgValid -eq 0
+            then
+                err "Option '$arg' is not recognized"
+            fi
+        fi
+    done
+}
+
+absolutify() {
+    FILE_PATH="${1}"
+    FILE_PATH_DIRNAME="$(dirname ${FILE_PATH})"
+    FILE_PATH_BASENAME="$(basename ${FILE_PATH})"
+    FILE_ABS_PATH="$(cd ${FILE_PATH_DIRNAME} && pwd)"
+    FILE_PATH="${FILE_ABS_PATH}/${FILE_PATH_BASENAME}"
+    # This is the return value
+    ABSOLUTIFIED="${FILE_PATH}"
+}
+
+msg "looking for install programs"
+need_cmd mkdir
+need_cmd printf
+need_cmd cut
+need_cmd grep
+need_cmd uname
+need_cmd tr
+need_cmd sed
+
+CFG_SRC_DIR="$(cd $(dirname $0) && pwd)"
+CFG_SELF="$0"
+CFG_ARGS="$@"
+
+HELP=0
+if [ "$1" = "--help" ]
+then
+    HELP=1
+    shift
+    echo
+    echo "Usage: $CFG_SELF [options]"
+    echo
+    echo "Options:"
+    echo
+else
+    step_msg "processing $CFG_SELF args"
+fi
+
+# Check for mingw or cygwin in order to special case $CFG_LIBDIR_RELATIVE.
+# This logic is duplicated from configure in order to get the correct libdir
+# for Windows installs.
+CFG_OSTYPE=$(uname -s)
+
+case $CFG_OSTYPE in
+
+    MINGW32*)
+        CFG_OSTYPE=pc-mingw32
+        ;;
+
+    MINGW64*)
+        # msys2, MSYSTEM=MINGW64
+        CFG_OSTYPE=w64-mingw32
+        ;;
+
+# Thad's Cygwin identifers below
+
+#   Vista 32 bit
+    CYGWIN_NT-6.0)
+        CFG_OSTYPE=pc-mingw32
+        ;;
+
+#   Vista 64 bit
+    CYGWIN_NT-6.0-WOW64)
+        CFG_OSTYPE=w64-mingw32
+        ;;
+
+#   Win 7 32 bit
+    CYGWIN_NT-6.1)
+        CFG_OSTYPE=pc-mingw32
+        ;;
+
+#   Win 7 64 bit
+    CYGWIN_NT-6.1-WOW64)
+        CFG_OSTYPE=w64-mingw32
+        ;;
+esac
+
+OPTIONS=""
+BOOL_OPTIONS=""
+VAL_OPTIONS=""
+
+# On windows we just store the libraries in the bin directory because
+# there's no rpath. This is where the build system itself puts libraries;
+# --libdir is used to configure the installation directory.
+# FIXME: Thise needs to parameterized over target triples. Do it in platform.mk
+CFG_LIBDIR_RELATIVE=lib
+if [ "$CFG_OSTYPE" = "pc-mingw32" ] || [ "$CFG_OSTYPE" = "w64-mingw32" ]
+then
+    CFG_LIBDIR_RELATIVE=bin
+fi
+
+flag uninstall "only uninstall from the installation prefix"
+opt verify 1 "verify that the installed binaries run correctly"
+valopt prefix "/usr/local" "set installation prefix"
+# NB This isn't quite the same definition as in `configure`.
+# just using 'lib' instead of CFG_LIBDIR_RELATIVE
+valopt libdir "${CFG_PREFIX}/${CFG_LIBDIR_RELATIVE}" "install libraries"
+valopt mandir "${CFG_PREFIX}/share/man" "install man pages in PATH"
+
+if [ $HELP -eq 1 ]
+then
+    echo
+    exit 0
+fi
+
+step_msg "validating $CFG_SELF args"
+validate_opt
+
+# OK, let's get installing ...
+
+# Sanity check: can we run the binaries?
+if [ -z "${CFG_DISABLE_VERIFY}" ]
+then
+    # Don't do this if uninstalling. Failure here won't help in any way.
+    if [ -z "${CFG_UNINSTALL}" ]
+    then
+        msg "verifying platform can run binaries"
+        "${CFG_SRC_DIR}/bin/cargo" -h > /dev/null
+        if [ $? -ne 0 ]
+        then
+            err "can't execute rustc binary on this platform"
+        fi
+    fi
+fi
+
+# Sanity check: can we can write to the destination?
+msg "verifying destination is writable"
+umask 022 && mkdir -p "${CFG_LIBDIR}"
+need_ok "can't write to destination. consider \`sudo\`."
+touch "${CFG_LIBDIR}/rust-install-probe" > /dev/null
+if [ $? -ne 0 ]
+then
+    err "can't write to destination. consider \`sudo\`."
+fi
+rm -f "${CFG_LIBDIR}/rust-install-probe"
+need_ok "failed to remove install probe"
+
+# Sanity check: don't install to the directory containing the installer.
+# That would surely cause chaos.
+msg "verifying destination is not the same as source"
+INSTALLER_DIR="$(cd $(dirname $0) && pwd)"
+PREFIX_DIR="$(cd ${CFG_PREFIX} && pwd)"
+if [ "${INSTALLER_DIR}" = "${PREFIX_DIR}" ]
+then
+    err "can't install to same directory as installer"
+fi
+
+# Using an absolute path to libdir in a few places so that the status
+# messages are consistently using absolute paths.
+absolutify "${CFG_LIBDIR}"
+ABS_LIBDIR="${ABSOLUTIFIED}"
+
+# The file name of the manifest we're going to create during install
+INSTALLED_MANIFEST="${ABS_LIBDIR}/cargo/manifest"
+
+# First, uninstall from the installation prefix.
+# Errors are warnings - try to rm everything in the manifest even if some fail.
+if [ -f "${INSTALLED_MANIFEST}" ]
+then
+    # Iterate through installed manifest and remove files
+    while read p; do
+        # The installed manifest contains absolute paths
+        msg "removing $p"
+        if [ -f "$p" ]
+        then
+            rm -f "$p"
+            if [ $? -ne 0 ]
+            then
+                warn "failed to remove $p"
+            fi
+        else
+            warn "supposedly installed file $p does not exist!"
+        fi
+    done < "${INSTALLED_MANIFEST}"
+
+    # If we fail to remove cargo below, then the installed manifest will
+    # still be full; the installed manifest needs to be empty before install.
+    msg "removing ${INSTALLED_MANIFEST}"
+    rm -f "${INSTALLED_MANIFEST}"
+    # For the above reason, this is a hard error
+    need_ok "failed to remove installed manifest"
+
+    # Remove 'rustlib' directory
+    msg "removing ${ABS_LIBDIR}/cargo"
+    rm -Rf "${ABS_LIBDIR}/cargo"
+    if [ $? -ne 0 ]
+    then
+        warn "failed to remove cargo"
+    fi
+else
+    # There's no manifest. If we were asked to uninstall, then that's a problem.
+    if [ -n "${CFG_UNINSTALL}" ]
+    then
+        err "unable to find installation manifest at ${CFG_LIBDIR}/cargo"
+    fi
+fi
+
+# If we're only uninstalling then exit
+if [ -n "${CFG_UNINSTALL}" ]
+then
+    echo
+    echo "    Cargo is uninstalled."
+    echo
+    exit 0
+fi
+
+# Create the installed manifest, which we will fill in with absolute file paths
+mkdir -p "${CFG_LIBDIR}/cargo"
+need_ok "failed to create cargo"
+touch "${INSTALLED_MANIFEST}"
+need_ok "failed to create installed manifest"
+
+# Now install, iterate through the new manifest and copy files
+while read p; do
+
+    # Decide the destination of the file
+    FILE_INSTALL_PATH="${CFG_PREFIX}/$p"
+
+    if echo "$p" | grep "^lib/" > /dev/null
+    then
+        pp=`echo $p | sed 's/^lib\///'`
+        FILE_INSTALL_PATH="${CFG_LIBDIR}/$pp"
+    fi
+
+    if echo "$p" | grep "^share/man/" > /dev/null
+    then
+        pp=`echo $p | sed 's/^share\/man\///'`
+        FILE_INSTALL_PATH="${CFG_MANDIR}/$pp"
+    fi
+
+    # Make sure there's a directory for it
+    umask 022 && mkdir -p "$(dirname ${FILE_INSTALL_PATH})"
+    need_ok "directory creation failed"
+
+    # Make the path absolute so we can uninstall it later without
+    # starting from the installation cwd
+    absolutify "${FILE_INSTALL_PATH}"
+    FILE_INSTALL_PATH="${ABSOLUTIFIED}"
+
+    # Install the file
+    msg "${FILE_INSTALL_PATH}"
+    if echo "$p" | grep "^bin/\\|/cargo-" > /dev/null
+    then
+        install -m755 "${CFG_SRC_DIR}/$p" "${FILE_INSTALL_PATH}"
+    else
+        install -m644 "${CFG_SRC_DIR}/$p" "${FILE_INSTALL_PATH}"
+    fi
+    need_ok "file creation failed"
+
+    # Update the manifest
+    echo "${FILE_INSTALL_PATH}" >> "${INSTALLED_MANIFEST}"
+    need_ok "failed to update manifest"
+
+# The manifest lists all files to install
+done < "${CFG_SRC_DIR}/${CFG_LIBDIR_RELATIVE}/cargo/manifest.in"
+
+# Sanity check: can we run the installed binaries?
+if [ -z "${CFG_DISABLE_VERIFY}" ]
+then
+    msg "verifying installed binaries are executable"
+    "${CFG_PREFIX}/bin/cargo" -h > /dev/null
+    if [ $? -ne 0 ]
+    then
+        ERR="can't execute installed rustc binary. "
+        ERR="${ERR}installation may be broken. "
+        ERR="${ERR}if this is expected then rerun install.sh with \`--disable-verify\` "
+        ERR="${ERR}or \`make install\` with \`--disable-verify-install\`"
+        err "${ERR}"
+    fi
+fi
+
+echo
+echo "    Cargo is ready to roll."
+echo
+
+